package kdxf

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"github.com/astaxie/beego"
	log "github.com/astaxie/beego/logs"
	"github.com/micro/go-micro/errors"
	"net"
	"os"
	"strconv"
	"sync"
	"time"
)

type kdxfTtsTask struct {
	client   net.Conn
	cache    sync.Map
	audioDir string
	task     chan tts_task
	result   chan kexf_result
	taskId   int32
}

type tts_cache struct {
	startTime time.Time
	fileName  string
}

type tts_task struct {
	text       string
	resultChan chan string
}

var ttsTask kdxfTtsTask

func Init() {

	ttsTask.result = make(chan kexf_result, 5)
	ttsTask.task = make(chan tts_task, 100)

	ttsTask.audioDir = beego.AppConfig.DefaultString("ttsPath", "/opt/ipcc/tts")

	go ttsTask.server()
	go ttsTask.taskProcess()
}

func getTtsArgs(name string, args map[string]interface{}) int32 {
	p, ok := args[name]
	if ok {
		switch p.(type) {
		case int:
			return int32(p.(int))
		case int32:
			return int32(p.(int32))
		case int64:
			return int32(p.(int64))
		case float32:
			ret, err := strconv.Atoi(fmt.Sprintf("%.0f", p.(float32)))
			if err != nil {
				log.Error("[[%v]:%v]get value failed:%v", name, p, err)
				return 50
			}
			return int32(ret)
		case float64:
			ret, err := strconv.Atoi(fmt.Sprintf("%.0f", p.(float64)))
			if err != nil {
				log.Error("[[%v]:%v]get value failed:%v", name, p, err)
				return 50
			}
			return int32(ret)
		default:
			return 50
		}
	}
	return 50
}

func ConvertTTS(args map[string]interface{}) (string, error) {

	pitch := getTtsArgs("pitch", args)
	speed := getTtsArgs("speed", args)
	volume := getTtsArgs("volume", args)

	text, ok := args["text"]
	if !ok {
		text = args["Text"]
	}

	return ttsTask.convertTTSTaskWithArgs(text.(string), pitch, speed, volume)

}

func AddNewConvertTTSTask(text string) (string, error) {

	return ttsTask.convertTTSTask(text)
}

func (t *kdxfTtsTask) convertTTSTask(text string) (string, error) {

	cache, ok := t.cache.Load(text)
	if ok {
		fileName := ""
		switch cache.(type) {
		case tts_cache:
			fileName = cache.(tts_cache).fileName
		}
		log.Debug("hit cache:%v,file:%v", text, fileName)
		return fileName, nil
	}

	t.taskId++
	start := time.Now()
	newTask := kdxf_head{
		sessionId: t.taskId,
		pitch:     48,
		speed:     51,
		volume:    50,
		text:      text,
		fileName:  fmt.Sprintf("TTS_%s_%d", start.Format("20060102150405"), 10000+t.taskId),
	}

	taskData := protoEnCodeHead(newTask)
	if t.client == nil {
		return "", errors.New("18001", "no kdxf client alive", 500)
	}

	send, err := t.client.Write(taskData.Bytes())
	if err != nil {
		log.Error("send task %v err:%v", t.taskId, err)
		return "", errors.New("18002", err.Error(), 500)
	}

	log.Debug("send[%v] to kdxf clent:%v,wait for result", taskData.String(), send)

	result := <-t.result
	if result.result != 0 {
		log.Error("tts[%v] convert failed:%v", text, t.taskId)
		return "", errors.New("18003", "tts convert failed", 500)
	}

	fileName := fmt.Sprintf(t.audioDir + newTask.fileName + ".wav")
	log.Debug("text[%v],recv result:%v,cost:%v", text, fileName, time.Since(start))

	newCache := tts_cache{
		fileName:  fileName,
		startTime: start,
	}

	t.cache.Store(text, newCache)

	return fileName, nil
}

func (t *kdxfTtsTask) server() {

	s, _ := net.Listen("tcp", "127.0.0.1:1237")
	for {
		c, err := s.Accept()
		if err != nil {
			log.Error("accept err:%v", err)
			time.Sleep(1 * time.Second)
			continue
		}

		log.Debug("new client connect from:%v", c.RemoteAddr())

		ttsTask.client = c
		go t.readClient()
	}
}

func (t *kdxfTtsTask) readClient() {

	for {

		result, err := protoDecodeResult(t.client)
		if err != nil {
			log.Error("recv err:%v", err)

			break
		}

		t.result <- result
	}
}

func (t *kdxfTtsTask) taskProcess() {

	log.Debug("tts process task start...")
	for {
		task := <-t.task
		file, err := t.convertTTSTask(task.text)
		if err != nil {
			log.Error("convert tts failed:%v ", err)
			task.resultChan <- ""
		} else {
			task.resultChan <- file
		}
	}
}

func (t *kdxfTtsTask) getCachName(text string, pitch, speed, volume int) string {

	return createCacheFileName(text, pitch, speed, volume)

}

func (t *kdxfTtsTask) convertTTSTaskWithArgs(text string, pitch, speed, volume int32) (string, error) {

	file, err := getCacheFile(text, int(pitch), int(speed), int(volume))
	if err == nil {
		log.Debug("hit cache:%v,file:%v", text, file)
		return file, nil
	}

	file = t.getCachName(text, int(pitch), int(speed), int(volume))

	t.taskId++
	start := time.Now()
	newTask := kdxf_head{
		sessionId: t.taskId,
		pitch:     pitch,
		speed:     speed,
		volume:    volume,
		text:      text,
		fileName:  file,
	}

	log.Debug("get args pitch:%v,speed:%v,volume:%v,get filename:%v", pitch, speed, volume, file)
	if t.client == nil {
		return "", errors.New("18002", "no tts clien alive", 500)
	}

	taskData := protoEnCodeHead(newTask)
	send, err := t.client.Write(taskData.Bytes())
	if err != nil {
		log.Error("send task %v err:%v", t.taskId, err)
		return "", err
	}

	log.Debug("send[%v] to kdxf clent:%v,wait for result", taskData.String(), send)

	result := <-t.result
	if result.result != 0 {
		log.Error("tts[%v] convert failed:%v", text, t.taskId)
		return "", errors.New("18003", "tts convert failed", 500)
	}

	log.Debug("text[%v],recv result:%v,cost:%v", text, file, time.Since(start))

	return file, nil
}

func createCacheFileName(text string, volume, speed, pitch int) string {
	hashStr := fmt.Sprintf("%s_%d_%d_%d", text, volume, speed, pitch)
	file := hashFile(hashStr)

	return file
}

func getCacheFile(text string, volume, speed, pitch int) (string, error) {

	hashStr := fmt.Sprintf("%s_%d_%d_%d", text, volume, speed, pitch)
	log.Debug("hash string:%v", hashStr)
	file := hashFile(hashStr)

	name := ttsTask.audioDir + "/" + file + ".wav"
	_, err := os.Stat(name)
	if err != nil {
		if os.IsNotExist(err) {
			return "", err
		}

		return "", err
	}

	return name, nil
}

func hashFile(text string) string {

	signByte := []byte(text)
	hash := md5.New()
	hash.Write(signByte)
	return hex.EncodeToString(hash.Sum(nil))
}
